/**
 *******************************************************************************
 * @file    tiny_kvdb_port.c
 * @brief   port functions for tiny kvdb.
 *******************************************************************************
 */

/* Includes ------------------------------------------------------------------*/

#include "string.h"
#include "hc32_ll.h"
#include "tiny_kvdb.h"

/* Private Defines -----------------------------------------------------------*/
#define     SECTOR_MASK                         (SECTOR_SIZE - 1U)
#define     SECTOR_START_ADDR(addr)             ((addr) & ~SECTOR_MASK)
#define     SECTOR_OF_ADDR(addr)                ((addr) / SECTOR_SIZE)

/* Private TypeDefs ----------------------------------------------------------*/

/* Private Macros ------------------------------------------------------------*/

/* Private Variables ---------------------------------------------------------*/

/* Extern Variables ----------------------------------------------------------*/

/* Private FunctionPrototypes ------------------------------------------------*/

/* Extern FunctionPrototypes -------------------------------------------------*/

/* Private Functions ---------------------------------------------------------*/
static uint32_t get_sector_count(uint32_t addr, uint32_t len)
{
    uint32_t end_addr = addr + len;
    uint32_t start_sector = SECTOR_OF_ADDR(addr);
    uint32_t end_sector = SECTOR_OF_ADDR(end_addr);

    return end_sector - start_sector + 1;
}

int flash_init (void)
{
    return 1;
}

int flash_read (uint32_t addr, void * p_dst, uint32_t len)
{
    if ((addr + len) > EFM_END_ADDR)
    {
        return -1;
    }

    if (LL_OK != EFM_ReadByte(addr, p_dst, len))
    {
        return -1;
    }

    return len;
}

int flash_write (uint32_t addr, void * p_src, uint32_t len)
{
    uint32_t data_write;
    uint32_t write_addr = addr;
    uint32_t remain, offset;
    const uint8_t *p = (const uint8_t *)p_src;
    int32_t result;

    if (p_src == 0 || len == 0)
    {
        return -1;
    }
    if ((addr + len) > EFM_END_ADDR)
    {
        return -1;
    }

    uint32_t start_sector = SECTOR_OF_ADDR(addr);
    uint32_t sector_count = get_sector_count(addr, len);

    EFM_REG_Unlock();
    EFM_FWMC_Cmd(ENABLE);           /* EFM_FWMC write enable */
    if (sector_count == 1)
    {
        EFM_SingleSectorOperateCmd(start_sector, ENABLE);
    }
    else
    {
        EFM_SequenceSectorOperateCmd(start_sector, sector_count, ENABLE);         /* Disable sector write protection */
    }

    uint32_t level = __get_PRIMASK();
    __set_PRIMASK(1);       /* disable irq */

    /* dest address is not word-aligned */
    if (write_addr & 0x03)
    {
        uint8_t temp_buf[4] __ALIGNED(4) = {0xFF, 0xFF, 0xFF, 0xFF};

        offset = write_addr & 0x03;
        remain = 4 - offset;
        write_addr = write_addr & 0xFFFFFFFCU;

        result = EFM_ReadByte(write_addr, temp_buf, offset);
        if (result != LL_OK)
        {
            goto end;
        }

        if (len < remain)
        {
            remain = len;
        }
        len -= remain;

        while (remain)
        {
            temp_buf[offset] = *p;
            offset++;
            p++;
            remain--;
        }
        data_write = *(uint32_t *)temp_buf;

        result = EFM_ProgramWord(write_addr, data_write);
        if (result != LL_OK)
        {
            goto end;
        }
        if (len == 0)
        {
            goto end;
        }

        write_addr += 4;
    }

    /* source address is word-aligned */
    if (IS_ADDR_ALIGN_WORD(p))
    {
        result = EFM_SequenceProgram(write_addr, (uint8_t *)p, len);
        if(result != LL_OK)
        {
            goto end;
        }
        p += len;
    }
    /* source address is not word-aligned */
    else
    {
        while (len >= 4)
        {
            data_write = ((uint32_t) *(p + 3) << 24) + ((uint32_t) *(p + 2) << 16) + ((uint32_t) *(p + 1) << 8) + *p;
            result = EFM_ProgramWord(write_addr, data_write);
            if (result != LL_OK)
            {
                goto end;
            }
            write_addr += 4;
            len -= 4;
            p += 4;
        }
        if (len != 0)
        {
            data_write = ((uint32_t) *(p + 3) << 24) + ((uint32_t) *(p + 2) << 16) + ((uint32_t) *(p + 1) << 8) + *p;
            data_write |= 0xFFFFFFFFU << (len * 8U);
            result = EFM_ProgramWord(write_addr, data_write);
            p += len;
        }
    }

end:
    __set_PRIMASK(level);       /* __enable_irq */
    if (sector_count == 1)
    {
        EFM_SingleSectorOperateCmd(start_sector, DISABLE);
    }
    else
    {
        EFM_SequenceSectorOperateCmd(start_sector, sector_count, DISABLE);     /* Enable sector write protection */
    }
    EFM_FWMC_Cmd(DISABLE);                                                     /* EFM_FWMC write disable */
    EFM_REG_Lock();

    if (result != LL_OK)
        return result;

    return p - (const uint8_t *)p_src;
}


int flash_erase (uint32_t addr, uint32_t len)
{
    int32_t result;
    uint32_t end_addr = addr + len;

    uint32_t start_sector = SECTOR_OF_ADDR(addr);
    uint32_t sector_count = get_sector_count(addr, len);
    addr = SECTOR_START_ADDR(addr);

    EFM_REG_Unlock();
    EFM_FWMC_Cmd(ENABLE);           /* EFM_FWMC write enable */
    if (sector_count == 1)
    {
        EFM_SingleSectorOperateCmd(start_sector, ENABLE);
    }
    else
    {
        EFM_SequenceSectorOperateCmd(start_sector, sector_count, ENABLE);         /* Disable sector write protection */
    }

    uint32_t level = __get_PRIMASK();
    __set_PRIMASK(1);       /* disable irq */

    while (addr < end_addr)
    {
        result = EFM_SectorErase(addr);
        if (result != LL_OK)
        {
            goto end;
        }

        addr += SECTOR_SIZE;
    }

end:
    __set_PRIMASK(level);       /* __enable_irq */

    if (sector_count == 1)
    {
        EFM_SingleSectorOperateCmd(start_sector, DISABLE);
    }
    else
    {
        EFM_SequenceSectorOperateCmd(start_sector, sector_count, DISABLE);     /* Enable sector write protection */
    }
    EFM_FWMC_Cmd(DISABLE);                                                     /* EFM_FWMC write disable */
    EFM_REG_Lock();

    if (result != LL_OK)
        return result;

    return len;
}

tiny_db_device_t hc32f4a0_onchip_flash =
{
    .device_id 	= 0x48433441,       /* HC4A */
    .start_addr = 1024*1024,
    .max_size = 1024*1024,
    .sector_size= SECTOR_SIZE,
    .write_unit = 4,
    
    .init = flash_init,
    .read = flash_read,
    .write = flash_write,
    .erase = flash_erase
};


/******************************** END OF FILE *********************************/


